package com.jorenwu.asymmetric;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.math.BigInteger;
import java.util.Random;

public class EIGamalengine
{
	private BigInteger p, g, x, y;
	private int nbits;
	PrimeTest pt = new MillerRabinPrimeTest();
	BaseCalculator bc = new BaseCalculator();

	public EIGamalengine(int nbits)
	{
		this.nbits = nbits;
	}

	public void keyGenerate()
	{
		// generate the key p
		for (;;)
		{
			p = bc.getBigRandomInt(nbits);

			if (!pt.primeTest(p, 15))
			{
				continue;
			}
			break;
		}

		// generate the key g
		g = bc.getBigRandomInt(nbits/2);

		// generate the key x
		Random rand = new Random(System.currentTimeMillis());
		x = new BigInteger(p.bitCount(), rand);
		x = (x.mod(p)).subtract(BigInteger.ONE);
		if (x.compareTo(p) == 0)
			x.subtract(BaseCalculator.TWO);
		// generate the key y
		y = g.modPow(x, p);
	}

	public void saveKey(String fileName)
	{
		try
		{
			BigInteger[] keyPair1 = new BigInteger[3];
			FileOutputStream f1 = new FileOutputStream(fileName + ".public");
			ObjectOutput s1 = new ObjectOutputStream(f1);
			keyPair1[0] = p;
			keyPair1[1] = g;
			keyPair1[2] = y;
			s1.writeObject(keyPair1);
			BigInteger[] keyPair2 = new BigInteger[5];
			FileOutputStream f2 = new FileOutputStream(fileName + ".private");
			ObjectOutput s2 = new ObjectOutputStream(f2);
			keyPair2[0] = p;
			keyPair2[1] = g;
			keyPair2[2] = x;
			keyPair2[3] = y;
			s2.writeObject(keyPair2);
		} catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}

	public void getFilePublicKey(String filename)
	{
		BigInteger[] privateKeyPair = new BigInteger[2];
		try
		{
			FileInputStream in = new FileInputStream(filename);
			ObjectInput s = new ObjectInputStream(in);
			privateKeyPair = (BigInteger[]) s.readObject();
			p = privateKeyPair[0];
			g = privateKeyPair[1];
			y = privateKeyPair[2];
		} catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}

	public void getFilePrivateKey(String filename)
	{
		BigInteger[] publicKeyPair = new BigInteger[5];
		try
		{
			FileInputStream in = new FileInputStream(filename);
			ObjectInput s = new ObjectInputStream(in);
			publicKeyPair = (BigInteger[]) s.readObject();
			p = publicKeyPair[0];
			g = publicKeyPair[1];
			x = publicKeyPair[2];
			y = publicKeyPair[3];
		} catch (Exception ex)
		{
			ex.printStackTrace();
		}
	}

	JorenEncoder encoder = new JorenEncoder();

	// Encoder encoder = new Encoder();

	public void blockEncrypto(Reader reader, /* OutputStream outputStream) */
	ObjectOutputStream outputStream)
	{
		if (p == null && g == null && y == null && reader == null
				&& outputStream == null)
		{
			System.out
					.println("there are some values you haven't initialized!");
			System.exit(0);
		}
		BigInteger k;
		// generate key k
		Random rand = new Random(System.currentTimeMillis());
		k = new BigInteger(p.bitCount(), rand);
		k = (k.mod(p)).subtract(BigInteger.ONE);
		if (k.compareTo(p) == 0)
			k.subtract(BaseCalculator.TWO);
		try
		{
			BigInteger c1 = g.modPow(k, p);
			outputStream.writeObject(c1);
			// encoder.outputBlockEncrypto(outputStream, c1);
			BigInteger c2;
			int ch = reader.read();
			while (ch >= 0)
			{
				c2 = encoder.inputBlockEncrypto(ch);
				c2 = y.modPow(k, p).multiply(c2).mod(p);
				encoder.outputBlockEncrypto(outputStream, c2);
				ch = reader.read();
			}
			outputStream.close();
			reader.close();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public void blockDecrypto(Writer writer, /* InputStream inputStream) */
	ObjectInputStream inputStream)
	{
		if (x == null && p == null && writer == null && inputStream == null)
		{
			System.out
					.println("there are some values you haven't initialized!");
			System.exit(0);
		}
		try
		{
			BigInteger c1 = (BigInteger) inputStream.readObject();
			// byte[] bt = new byte[129];
			// inputStream.read(bt);
			// BigInteger c1 = encoder.inputBlockDecrypto(bt);
			BigInteger bi, m;
			Media c2 = (Media) inputStream.readObject();
			while (/* inputStream.read(bt) >= 0)// */true)
			{
				bi = encoder.inputBlockDecrypto(/* bt);// */c2);
				// m = bi.divide(c1.modPow(x, p)).mod(p);
				m = bi.multiply(c1.modPow(x, p).modInverse(p)).mod(p);
				encoder.outputBlockDecrypto(writer, m);
				try
				{
					c2 = (Media) inputStream.readObject();
				} catch (Exception ex)
				{
					break;
				}
			}
			inputStream.close();
			writer.close();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public BigInteger getP()
	{
		return p;
	}

	public void setP(BigInteger p)
	{
		this.p = p;
	}

	public BigInteger getG()
	{
		return g;
	}

	public void setG(BigInteger g)
	{
		this.g = g;
	}

	public BigInteger getX()
	{
		return x;
	}

	public void setX(BigInteger x)
	{
		this.x = x;
	}

	public BigInteger getY()
	{
		return y;
	}

	public void setY(BigInteger y)
	{
		this.y = y;
	}
}
